﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using TomanuExtensions;
using YAXLib;

namespace RaytracerLib.MathLib
{
    [DebuggerStepThrough]
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct ColorFloat
    {
        [YAXAttributeForClass]
        public readonly float R;

        [YAXAttributeForClass]
        public readonly float G;

        [YAXAttributeForClass]
        public readonly float B;

        #region Colors
        public static readonly ColorFloat Black = new ColorFloat(Color.Black);
        public static readonly ColorFloat White = new ColorFloat(Color.White);
        public static readonly ColorFloat SRGB_Red = new ColorFloat(Color.Red);
        public static readonly ColorFloat SRGB_Blue = new ColorFloat(Color.Blue);
        public static readonly ColorFloat SRGB_Green = new ColorFloat(Color.Green);
        public static readonly ColorFloat SRGB_LightGreen = new ColorFloat(Color.LightGreen);
        public static readonly ColorFloat SRGB_Yellow = new ColorFloat(Color.Yellow);
        public static readonly ColorFloat SRGB_Pink = new ColorFloat(Color.Pink);
        #endregion

        public ColorFloat(float a_grey)
        {
            R = a_grey;
            G = a_grey;
            B = a_grey;

            Debug.Assert(IsNumber);
        }

        public ColorFloat(double a_grey)
        {
            R = (float)a_grey;
            G = (float)a_grey;
            B = (float)a_grey;

            Debug.Assert(IsNumber);
        }

        public ColorFloat(Color a_color)
        {
            R = a_color.R / 255.0f;
            G = a_color.G / 255.0f;
            B = a_color.B / 255.0f;

            Debug.Assert(IsNumber);
        }

        public ColorFloat(ColorFloat a_color)
        {
            R = a_color.R;
            G = a_color.G;
            B = a_color.B;

            Debug.Assert(IsNumber);
        }

        public ColorFloat(float a_r, float a_g, float a_b)
        {
            R = a_r;
            G = a_g;
            B = a_b;

            Debug.Assert(IsNumber);
        }

        public ColorFloat(double a_r, double a_g, double a_b)
        {
            R = (float)a_r;
            G = (float)a_g;
            B = (float)a_b;

            Debug.Assert(IsNumber);
        }

        public override int GetHashCode()
        {
            return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode();
        }

        public static bool operator ==(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R == a_b.R) && (a_a.G == a_b.G) && (a_a.B == a_b.B);
        }

        public static bool operator !=(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R != a_b.R) || (a_a.G != a_b.G) || (a_a.B != a_b.B);
        }

        public override bool Equals(object a_obj)
        {
            if (a_obj == null)
                return false;

            if (!typeof(ColorFloat).Equals(a_obj.GetType()))
                return false;
            ColorFloat color = (ColorFloat)a_obj;

            return (R == color.R) && (G == color.G) && (B == color.B);
        }

        public bool Equals(ColorFloat a_color)
        {
            return (R == a_color.R) && (G == a_color.G) && (B == a_color.B);
        }

        public static ColorFloat operator +(ColorFloat a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_a.R + a_b.R, a_a.G + a_b.G, a_a.B + a_b.B);
        }

        public static ColorFloat operator -(ColorFloat a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_a.R - a_b.R, a_a.G - a_b.G, a_a.B - a_b.B);
        }

        public static ColorFloat operator -(ColorFloat a_a)
        {
            return new ColorFloat(-a_a.R, -a_a.G, -a_a.B);
        }

        public static ColorFloat operator *(ColorFloat a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_a.R * a_b.R, a_a.G * a_b.G, a_a.B * a_b.B);
        }

        public static ColorFloat operator *(ColorFloat a_a, double a_b)
        {
            return new ColorFloat(a_a.R * a_b, a_a.G * a_b, a_a.B * a_b);
        }

        public static ColorFloat operator *(double a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_b.R * a_a, a_b.G * a_a, a_b.B * a_a);
        }

        public static ColorFloat operator /(ColorFloat a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_a.R / a_b.R, a_a.G / a_b.G, a_a.B / a_b.B);
        }

        public static ColorFloat operator /(ColorFloat a_a, double a_b)
        {
            return new ColorFloat(a_a.R / a_b, a_a.G / a_b, a_a.B / a_b);
        }

        public static ColorFloat operator /(double a_a, ColorFloat a_b)
        {
            return new ColorFloat(a_a / a_b.R, a_a / a_b.G, a_a / a_b.B);
        }

        public ColorFloat Neg
        {
            get
            {
                return new ColorFloat(1 - R, 1 - G, 1 - B);
            }
        }

        public double Avg
        {
            get
            {
                return (R + G + B) / 3;
            }
        }

        private bool IsNumber
        {
            get
            {
                return R.IsNumber() && G.IsNumber() && B.IsNumber();
            }
        }

        public ColorFloat AboveZero
        {
            get
            {
                return new ColorFloat(
                    (R >= 0) ? R : 0, 
                    (G >= 0) ? G : 0, 
                    (B >= 0) ? B : 0);
            }
        }

        public Color SystemColor
        {
            get
            {
                return Color.FromArgb(
                    NumberExtensions.Limit((R * 255).Round(), 0, 255), 
                    NumberExtensions.Limit((G * 255).Round(), 0, 255), 
                    NumberExtensions.Limit((B * 255).Round(), 0, 255));
            }
        }

        public override string ToString()
        {
            var colors = from p in typeof(Color).GetProperties()
                         where p.CanRead
                         where !p.CanWrite
                         where p.GetGetMethod().IsStatic
                         where p.GetGetMethod().IsPublic
                         where p.PropertyType == typeof(Color)
                         select p;

            foreach (var p in colors)
            {
                Color cc = (Color)p.GetGetMethod().Invoke(null, null);

                if (cc.A == 0)
                    continue;

                if (this.IsAlmostRelativeEquals(new ColorFloat(cc)))
                    return p.Name;
            }

            return String.Format("R: {0}, G: {1}, B: {2}",
                R.ToString(CultureInfo.InvariantCulture),
                G.ToString(CultureInfo.InvariantCulture),
                B.ToString(CultureInfo.InvariantCulture));
        }

        public bool IsAlmostRelativeEquals(ColorFloat a_c, float a_precision = Constants.COLOR_PRECISION)
        {
            return R.IsAlmostRelativeEquals(a_c.R, a_precision) && G.IsAlmostRelativeEquals(a_c.G, a_precision) &&
                B.IsAlmostRelativeEquals(a_c.B, a_precision);
        }

        public static bool operator <(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R < a_b.R) && (a_a.G < a_b.G) && (a_a.B < a_b.B);
        }

        public static bool operator <=(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R <= a_b.R) && (a_a.G <= a_b.G) && (a_a.B <= a_b.B);
        }

        public static bool operator >(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R > a_b.R) && (a_a.G > a_b.G) && (a_a.B > a_b.B);
        }

        public static bool operator >=(ColorFloat a_a, ColorFloat a_b)
        {
            return (a_a.R >= a_b.R) && (a_a.G >= a_b.G) && (a_a.B >= a_b.B);
        }
    }
}